﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using DbGenerator.Model.attributes.types;
using System.IO;
using DbGenerator.Utilities;
using DbGenerator.Model.units.constraint.root;
using DbGenerator.Factory.events;
using DbGenerator.Factory.concrete.rsc;

namespace DbGenerator.Factory.concrete
{
    public class TConcreteWriter
    {
        private class TConstraintInfoBase
        {
            #region ctors
            public TConstraintInfoBase(string constraintName)
            {
                _ConstraintName = constraintName;
            }
            public TConstraintInfoBase()
            {

            }
            #endregion

            #region properties
            private string _ConstraintName;
            public string ConstraintName
            {
                get { return _ConstraintName; }
                protected set { _ConstraintName = value; }
            }
            #endregion
        }

        #region TColumnInfo class
        private class TColumnInfo
        {
            #region ctors
            public TColumnInfo(SqlDataReader rdr)
            {
                _TableSchema = rdr["TABLE_SCHEMA"].ToString();
                _TableName = rdr["TABLE_NAME"].ToString();
                _ColumnName = rdr["COLUMN_NAME"].ToString();
                _ColumnDefault = rdr["COLUMN_DEFAULT"];
                _IsNullable = Convert.ToBoolean(rdr["IS_NULLABLE"]);
                _AutoIncrement = Convert.ToBoolean(rdr["AUTO_INCREMENT"]);
                _DataType = SDbTypeUtilities.GetSqlDbType(rdr["DATA_TYPE"].ToString());
                int max = 0;
                _MaxLength =
                    (int.TryParse(rdr["CHARACTER_MAXIMUM_LENGTH"].ToString(), out max))
                    ? max : 0;
            }
            #endregion

            #region properties
            private string _TableSchema;
            public string TableSchema
            {
                get { return _TableSchema; }
            }
            private string _TableName;
            public string TableName
            {
                get { return _TableName; }
            }

            private string _ColumnName;
            public string ColumnName
            {
                get { return _ColumnName; }
            }

            private object _ColumnDefault;
            public object ColumnDefault
            {
                get { return _ColumnDefault; }
            }

            private bool _AutoIncrement;
            public bool AutoIncrement
            {
                get { return _AutoIncrement; }
            }

            private bool _IsNullable;
            public bool IsNullable
            {
                get { return _IsNullable; }
            }

            private SqlDbType _DataType;
            public SqlDbType DataType
            {
                get { return _DataType; }
            }

            private int _MaxLength;
            public int MaxLength
            {
                get { return _MaxLength; }
            }
            #endregion
        }
        #endregion

        #region constraint info classes
        private class TFKInfo : TConstraintInfoBase
        {
            #region ctors
            public TFKInfo(SqlDataReader rdr)
            {
                
                _BaseTableName = rdr["BASE_TABLE_NAME"].ToString();
                _BaseColumnName = rdr["BASE_COLUMN_NAME"].ToString();
                _ReferencedSchemaName = rdr["REFERENCED_SCHEMA_NAME"].ToString();
                _ReferencedPKName = rdr["REFERENCED_PK_NAME"].ToString();
                _ReferencedTableName = rdr["REFERENCED_TABLE_NAME"].ToString();
                _ReferencedColumnName = rdr["REFERENCED_COLUMN_NAME"].ToString();
                _DeleteRule = (EFKRule)(Convert.ToByte(rdr["DELETE_RULE_CODE"]));
                _UpdateRule = (EFKRule)(Convert.ToByte(rdr["UPDATE_RULE_CODE"]));
                ConstraintName = rdr["FK_NAME"].ToString();
            }
            #endregion

            #region properties
            private string _ReferencedSchemaName;
            public string ReferencedSchemaName
            {
                get { return _ReferencedSchemaName; }
            }

            private string _BaseTableName;
            public string BaseTableName
            {
                get { return _BaseTableName; }
            }

            private string _BaseColumnName;
            public string BaseColumnName
            {
                get { return _BaseColumnName; }
            }

            private string _ReferencedPKName;
            public string ReferencedPKName
            {
                get { return _ReferencedPKName; }
            }

            private string _ReferencedTableName;
            public string ReferencedTableName
            {
                get { return _ReferencedTableName; }
            }

            private string _ReferencedColumnName;
            public string ReferencedColumnName
            {
                get { return _ReferencedColumnName; }
            }

            private EFKRule _DeleteRule;
            public EFKRule DeleteRule
            {
                get { return _DeleteRule; }
            }

            private EFKRule _UpdateRule;
            public EFKRule UpdateRule
            {
                get { return _UpdateRule; }
            }
            #endregion
        }
        private class TPKInfo : TConstraintInfoBase
        {
            #region ctors
            public TPKInfo(SqlDataReader rdr)
            {
                _PKColumnName = rdr["PK_COLUMN_NAME"].ToString();
                ConstraintName = rdr["PK_NAME"].ToString();
            }
            #endregion

            #region properties
            private string _PKColumnName;
            public string PKColumnName
            {
                get { return _PKColumnName; }
            }
            #endregion
        }
        private class TUQInfo : TConstraintInfoBase
        {
            #region ctors
            public TUQInfo(SqlDataReader rdr)
            {
                _UQColumnName = rdr["UQ_COLUMN_NAME"].ToString();
                ConstraintName = rdr["UQ_NAME"].ToString();
            }
            #endregion

            #region properties
            private string _UQColumnName;
            public string UQColumnName
            {
                get { return _UQColumnName; }
            }
            #endregion
        }
        private class TDKInfo : TConstraintInfoBase
        {
            #region ctors
            public TDKInfo(SqlDataReader rdr)
            {
                _DKColumnName = rdr["DK_COLUMN_NAME"].ToString();
                _DefaultValue = rdr["DK_DEFINITION"].ToString();
                ConstraintName = rdr["DK_NAME"].ToString();
            }
            #endregion

            #region properties
            private object _DefaultValue;
            public object DefaultValue
            {
                get { return _DefaultValue; }
            }

            private string _DKColumnName;
            public string DKColumnName
            {
                get { return _DKColumnName; }
            }
            #endregion
        }
        private class TCKInfo : TConstraintInfoBase
        {
            #region ctors
            public TCKInfo(SqlDataReader rdr)
            {
                _BaseTableSchema = rdr["BASE_TABLE_SCHEMA"].ToString();
                _BaseTableCatalog = rdr["BASE_TABLE_CATALOG"].ToString();
                _BaseColumnName = rdr["BASE_COLUMN_NAME"].ToString();
                ConstraintName = rdr["CK_NAME"].ToString();
                _CKDefinition = rdr["CK_DEFINITION"].ToString();
            }
            #endregion

            #region properties
            private string _BaseTableSchema;
            public string BaseTableSchema
            {
                get { return _BaseTableSchema; }
            }

            private string _BaseTableCatalog;
            public string BaseTableCatalog
            {
                get { return _BaseTableCatalog; }
            }

            private string _BaseColumnName;
            public string BaseColumnName
            {
                get { return _BaseColumnName; }
            }

            private string _CKDefinition;
            public string CKDefinition
            {
                get { return _CKDefinition; }
            }
            #endregion
        }

        #endregion

        #region public methods
        public Type[] WriteConcreteDll(TFactoryArgs gene)
        {
            string databaseName = gene.Database.DatabaseName;
            string assemblyName = gene.AssemblyName;
            SqlConnection masterConnection = gene.MasterConnection;

            string[] tableNames = GetTableNames(databaseName, masterConnection);

            AssemblyName concreteName = new AssemblyName(assemblyName);
            AssemblyBuilder concreteBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(
                    concreteName
                    , AssemblyBuilderAccess.RunAndSave);

            ModuleBuilder entityModuleBuilder =
                concreteBuilder.DefineDynamicModule(concreteName.Name, assemblyName);

            List<Type> writtenTypes = new List<Type>();

            int tableCount = tableNames.Length;
            gene.GenerationTurtle.Start(tableCount);

            for (int i = 0; i < tableCount; i++)
            {
                //GetInformations for columns, fks and pks:
                TColumnInfo[] columnInfos
                    = GetColumnInfos(databaseName, masterConnection, tableNames[i]);
                string tableSchema = columnInfos[0].TableSchema;
                TFKInfo[] fkInfos
                    = GetFKInfos(databaseName, masterConnection, tableNames[i]);
                TPKInfo[] pkInfos
                    = GetPKInfos(databaseName, masterConnection, tableNames[i]);
                TUQInfo[] uqInfos
                    = GetUQInfos(databaseName, masterConnection, tableNames[i]);
                TDKInfo[] dkInfos
                    = GetDKInfos(databaseName, masterConnection, tableNames[i]);
                TCKInfo[] ckInfos
                    = GetCKInfos(databaseName, masterConnection, tableNames[i]);
                //entityType TStok
                TypeBuilder entityTypeBuilder =
                    entityModuleBuilder.DefineType("T" + tableNames[i], TypeAttributes.Public);

                //[TEntityAttribute(string entityName)]
                ConstructorInfo entityAttributeConstructorInfo =
                    typeof(TEntityAttribute).GetConstructor(new Type[] { typeof(string), typeof(string) });
                CustomAttributeBuilder entityAttributeBuilder =
                    new CustomAttributeBuilder(entityAttributeConstructorInfo
                        , new object[] { tableNames[i], tableSchema });
                entityTypeBuilder.SetCustomAttribute(entityAttributeBuilder);

                //Loop between columns to add them to type:
                int columnCount = columnInfos.Length;
                for (int j = 0; j < columnCount; j++)
                {
                    TColumnInfo curColumnInfo = columnInfos[j];
                    //TColumnAttribute for entity column:
                    ConstructorInfo columnAttributeConstructorInfo =
                        typeof(TColumnAttribute)
                        .GetConstructor(new Type[] { typeof(string) });
                    CustomAttributeBuilder columnAttributeBuilder =
                        new CustomAttributeBuilder(columnAttributeConstructorInfo
                            , new object[] { curColumnInfo.ColumnName }
                            , TColumnAttribute.GetNamedProperties()
                            , new object[] { 
                                curColumnInfo.AutoIncrement, 
                                curColumnInfo.DataType,
                                curColumnInfo.IsNullable,
                                curColumnInfo.MaxLength});

                    //Get return type:
                    Type columnReturnType = GetColumnTypeFromColumnInfo(columnInfos[j]);

                    //Build field:
                    FieldBuilder columnFieldBuilder
                        = entityTypeBuilder.DefineField(
                            curColumnInfo.ColumnName
                            , columnReturnType
                            , FieldAttributes.Private);

                    //Define Property:
                    PropertyBuilder columnPropertyBuilder
                        = entityTypeBuilder.DefineProperty(
                            curColumnInfo.ColumnName
                            , System.Reflection.PropertyAttributes.HasDefault
                            , columnReturnType
                            , Type.EmptyTypes);

                    //Set TColumnAttribute for this property:
                    columnPropertyBuilder.SetCustomAttribute(columnAttributeBuilder);

                    if (ColumnHasPK(curColumnInfo.ColumnName, pkInfos))
                    {
                        //if has:
                        //Set TPKAttribute:
                        ConstructorInfo columnPKConstructorInfo
                            = typeof(TPKAttribute).GetConstructor(Type.EmptyTypes);
                        CustomAttributeBuilder columnPKAttributeBuilder
                            = new CustomAttributeBuilder(columnPKConstructorInfo
                                , new object[] { });
                        columnPropertyBuilder.SetCustomAttribute(columnPKAttributeBuilder);
                    }

                    //Find TUQAttribute for this column:
                    if (ColumnHasUQ(curColumnInfo.ColumnName, uqInfos))
                    {
                        //if has; set uqattribute
                        ConstructorInfo uqConstraintConstructorInfo
                            = typeof(TUQAttribute).GetConstructor(Type.EmptyTypes);
                        CustomAttributeBuilder uqConstraintAttributeBuilder
                            = new CustomAttributeBuilder(uqConstraintConstructorInfo
                                , new object[] { });
                        columnPropertyBuilder.SetCustomAttribute(uqConstraintAttributeBuilder);
                    }

                    //Find TFKAttribute for this column:
                    TFKInfo currentFKInfo = GetColumnFK(curColumnInfo.ColumnName, fkInfos);
                    if (currentFKInfo != null)
                    {
                        //Set TFKAttribute for property:
                        ConstructorInfo fkConstraintConstructorInfo
                            = typeof(TFKAttribute).GetConstructor(
                            new Type[] { typeof(string), typeof(string), typeof(string), typeof(EFKRule), typeof(EFKRule) }
                            );
                        CustomAttributeBuilder fkCustomAttributeBuilder
                            = new CustomAttributeBuilder(fkConstraintConstructorInfo
                                , new object[] {
                                    currentFKInfo.ReferencedSchemaName
                                    , currentFKInfo.ReferencedTableName
                                    , currentFKInfo.ReferencedColumnName
                                    , currentFKInfo.DeleteRule
                                    , currentFKInfo.UpdateRule });
                        //Set custom attribute(fk)
                        columnPropertyBuilder.SetCustomAttribute(fkCustomAttributeBuilder);
                    }

                    //Find TDKAttribute for this column if has:
                    TDKInfo columnDK = GetColumnDK(curColumnInfo.ColumnName, dkInfos);
                    if (columnDK != null)
                    {
                        ConstructorInfo dkConstraintConstructorInfo
                            = typeof(TDKAttribute).GetConstructor(new Type[] { typeof(object) });
                        CustomAttributeBuilder dkConstraintAttributeBuilder
                            = new CustomAttributeBuilder(dkConstraintConstructorInfo
                                , new object[] { columnDK.DefaultValue });
                        columnPropertyBuilder.SetCustomAttribute(dkConstraintAttributeBuilder);
                    }

                    //Find TCKAttribute for this column if exists:
                    TCKInfo columnCK = GetColumnCK(curColumnInfo.ColumnName, ckInfos);
                    if (columnCK != null)
                    {
                        ConstructorInfo ckConstraintConstructorInfo
                            = typeof(TCKAttribute).GetConstructor(new Type[] { typeof(string) });
                        CustomAttributeBuilder ckConstraintAttributeBuilder
                            = new CustomAttributeBuilder(ckConstraintConstructorInfo
                                , new object[] { columnCK.CKDefinition });
                        columnPropertyBuilder.SetCustomAttribute(ckConstraintAttributeBuilder);
                    }

                    //Define fields:
                    //first: method attributes for field
                    MethodAttributes columnPropertyGetSetAttribute =
                        MethodAttributes.Public
                        | MethodAttributes.SpecialName
                        | MethodAttributes.HideBySig;

                    //get method:
                    MethodBuilder columnPropertyGetMethodBuilder
                         = entityTypeBuilder.DefineMethod(
                            "get_" + curColumnInfo.ColumnName
                            , columnPropertyGetSetAttribute
                            , CallingConventions.Standard
                            , columnReturnType
                            , Type.EmptyTypes);

                    //Generate get IL:
                    ILGenerator columnPropertyGetIL
                        = columnPropertyGetMethodBuilder.GetILGenerator();
                    columnPropertyGetIL.Emit(OpCodes.Ldarg_0);
                    columnPropertyGetIL.Emit(OpCodes.Ldfld, columnFieldBuilder);
                    columnPropertyGetIL.Emit(OpCodes.Ret);


                    //set method:
                    MethodBuilder columnPropertySetMethodBuilder
                         = entityTypeBuilder.DefineMethod(
                            "set_" + curColumnInfo.ColumnName
                            , columnPropertyGetSetAttribute
                            , CallingConventions.Standard
                            , null
                            , new Type[] { columnReturnType });

                    //Generate set IL:
                    ILGenerator columnPropertySetIL
                        = columnPropertySetMethodBuilder.GetILGenerator();
                    columnPropertySetIL.Emit(OpCodes.Ldarg_0);
                    columnPropertySetIL.Emit(OpCodes.Ldarg_1);
                    columnPropertySetIL.Emit(OpCodes.Stfld, columnFieldBuilder);
                    columnPropertySetIL.Emit(OpCodes.Ret);

                    columnPropertyBuilder.SetGetMethod(columnPropertyGetMethodBuilder);
                    columnPropertyBuilder.SetSetMethod(columnPropertySetMethodBuilder);
                }
                writtenTypes.Add(entityTypeBuilder.CreateType());
                gene.GenerationTurtle.MakeProgress(i);
            }

            concreteBuilder.Save(assemblyName + ".dll");
            gene.GenerationTurtle.Stop();
            return writtenTypes.ToArray();
        }
        #endregion

        #region private methods
        private TCKInfo GetColumnCK(string columnName, TCKInfo[] ckInfos)
        {
            int ckInfoCount = ckInfos.Length;
            for (int i = 0; i < ckInfoCount; i++)
            {
                if (ckInfos[i].BaseColumnName == columnName)
                    return ckInfos[i];
            }
            return null;
        }
        private TDKInfo GetColumnDK(string columnName, TDKInfo[] dkInfos)
        {
            int dkInfoLength = dkInfos.Length;
            for (int i = 0; i < dkInfoLength; i++)
            {
                if (dkInfos[i].DKColumnName == columnName)
                    return dkInfos[i];
            }
            return null;
        }
        private TFKInfo GetColumnFK(string columnName, TFKInfo[] fkInfos)
        {
            foreach (TFKInfo fk in fkInfos)
            {
                if (fk.BaseColumnName == columnName)
                    return fk;
            }
            return null;
        }
        private bool ColumnHasPK(string columnName, TPKInfo[] pkInfos)
        {
            int uqInfoCount = pkInfos.Length;
            for (int i = 0; i < uqInfoCount; i++)
            {
                if (pkInfos[i].PKColumnName == columnName)
                    return true;
            }
            return false;
        }
        private bool ColumnHasUQ(string columnName, TUQInfo[] uqInfos)
        {
            int uqInfoCount = uqInfos.Length;
            for (int i = 0; i < uqInfoCount; i++)
            {
                if (uqInfos[i].UQColumnName == columnName)
                    return true;
            }
            return false;
        }
        private TDKInfo[] GetDKInfos(string databaseName, SqlConnection masterConnection, string tableName)
        {
            List<TDKInfo> dkInfos = new List<TDKInfo>();

            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.Connection.Open();
            command.CommandText = String.Format(CommandTexts.dkinfo_script, databaseName, tableName);
            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);

            while (rdr.Read())
            {
                dkInfos.Add(new TDKInfo(rdr));
            }
            rdr.Close();

            return dkInfos.ToArray();
        }
        private TCKInfo[] GetCKInfos(string databaseName, SqlConnection masterConnection, string tableName)
        {
            List<TCKInfo> ckInfos = new List<TCKInfo>();

            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.Connection.Open();
            command.CommandText = String.Format(CommandTexts.ckinfo_script, databaseName, tableName);
            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);

            while (rdr.Read())
            {
                ckInfos.Add(new TCKInfo(rdr));
            }
            rdr.Close();

            return ckInfos.ToArray();
        }
        private TUQInfo[] GetUQInfos(string databaseName, SqlConnection masterConnection, string tableName)
        {
            List<TUQInfo> uqInfos = new List<TUQInfo>();

            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.Connection.Open();
            command.CommandText = String.Format(CommandTexts.uqinfo_script, databaseName, tableName);

            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                uqInfos.Add(new TUQInfo(rdr));
            }
            rdr.Close();

            return uqInfos.ToArray();
        }
        private TPKInfo[] GetPKInfos(string databaseName, SqlConnection masterConnection, string tableName)
        {
            List<TPKInfo> pkInfos = new List<TPKInfo>();

            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.Connection.Open();
            command.CommandText = String.Format(CommandTexts.pkinfo_script, databaseName, tableName);

            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                pkInfos.Add(new TPKInfo(rdr));
            }
            rdr.Close();

            return pkInfos.ToArray();
        }
        private TFKInfo[] GetFKInfos(string databaseName, SqlConnection masterConnection, string tableName)
        {
            List<TFKInfo> fkInfoList = new List<TFKInfo>();

            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.CommandText = String.Format(CommandTexts.fkinfo_script, databaseName, tableName);
            command.Connection.Open();

            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                fkInfoList.Add(new TFKInfo(rdr));
            }

            rdr.Close();
            return fkInfoList.ToArray();
        }
        private static TColumnInfo[] GetColumnInfos(string databaseName, SqlConnection masterConnection, string tableName)
        {
            List<TColumnInfo> columnInfos = new List<TColumnInfo>();

            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.CommandText =
                   String.Format(CommandTexts.colinfo_script, databaseName, tableName);

            command.Connection.Open();
            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                columnInfos.Add(new TColumnInfo(rdr));
            }
            rdr.Close();
            return columnInfos.ToArray();
        }
        private static string[] GetTableNames(string databaseName, SqlConnection masterConnection)
        {
            List<string> tableNames = new List<string>();
            SqlCommand command = new SqlCommand();
            command.Connection = masterConnection;
            command.CommandText =
                String.Format(CommandTexts.tablesinfo_script, databaseName);

            command.Connection.Open();
            SqlDataReader rdr = command.ExecuteReader(CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                tableNames.Add(rdr["TABLE_NAME"].ToString());
            }
            rdr.Close();
            return tableNames.ToArray();
        }
        private Type GetColumnTypeFromColumnInfo(TColumnInfo ci)
        {
            switch (ci.DataType)
            {
                case SqlDbType.BigInt:
                    return typeof(long);
                case SqlDbType.Binary:
                    return typeof(Stream);
                case SqlDbType.Bit:
                    return typeof(Boolean);
                case SqlDbType.Char:
                    return typeof(Char);
                case SqlDbType.Date:
                case SqlDbType.DateTime:
                case SqlDbType.DateTime2:
                case SqlDbType.DateTimeOffset:
                    return typeof(DateTime);
                case SqlDbType.Decimal:
                    return typeof(Decimal);
                case SqlDbType.Float:
                    return typeof(float);
                case SqlDbType.Image:
                    return typeof(Stream);
                case SqlDbType.Int:
                    return typeof(Int32);
                case SqlDbType.Money:
                    return typeof(Double);
                case SqlDbType.NChar:
                case SqlDbType.NText:
                case SqlDbType.NVarChar:
                    return typeof(String);
                case SqlDbType.Real:
                    return typeof(Double);
                case SqlDbType.SmallDateTime:
                    return typeof(DateTime);
                case SqlDbType.SmallInt:
                    return typeof(short);
                case SqlDbType.SmallMoney:
                    return typeof(float);
                case SqlDbType.Structured:
                    return typeof(object);
                case SqlDbType.Text:
                    return typeof(String);
                case SqlDbType.Time:
                    return typeof(DateTime);
                case SqlDbType.Timestamp:
                    return typeof(DateTime);
                case SqlDbType.TinyInt:
                    return typeof(byte);
                case SqlDbType.Udt:
                    return typeof(object);
                case SqlDbType.UniqueIdentifier:
                    return typeof(Guid);
                case SqlDbType.VarBinary:
                    return typeof(Stream);
                case SqlDbType.VarChar:
                    return typeof(String);
                case SqlDbType.Variant:
                    return typeof(object);
                case SqlDbType.Xml:
                    return typeof(object);
                default:
                    return typeof(object);
            }
        }
        #endregion
    }
}
